﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace F2B.Orchard.Schedule.Providers
{
    public static class EnumerableExtensions
    {
        // This is very similar to SelectMany except that it doesn't try to enumerate all the sources at once, but returns the results from the enumerators in order
        // It is assumed that the enumerables returned by selector are already ordered, which is the case for any of the date providers.

        public static IEnumerable<TResult> MergeOrdered<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector)
        {
            Dictionary<IEnumerator<TResult>, bool> enumerators =
                source
                .Select(s => selector(s).GetEnumerator())
                .Where(e => e.MoveNext())
                .ToDictionary(e => e, e => true);

            while (enumerators.Any(e => e.Value))
            {
                var smallest = enumerators.Where(e => e.Value).OrderBy(e => e.Key.Current).First();
                yield return smallest.Key.Current;
                enumerators[smallest.Key] = smallest.Key.MoveNext();
            }
        }

        public static IEnumerable<TResult> IntersectOrdered<TSource, TResult>(
            this IEnumerable<TSource> source,
            Func<TSource, IEnumerable<TResult>> selector) where TResult : IEquatable<TResult>
        {
            List<IEnumerator<TResult>> enumerators = source.Select(s => selector(s).GetEnumerator()).ToList();
            // initialize enumators

            bool done = enumerators.Any(e => !e.MoveNext());

            while (!done)
            {
                var smallest = enumerators.OrderBy(e => e.Current).First();
                if (enumerators.All(e => e.Current.Equals(smallest.Current))) yield return smallest.Current;
                done = !smallest.MoveNext();
            }
        }

        public static IEnumerable<T> DifferenceOrdered<T>(this IEnumerable<T> include, IEnumerable<T> exclude) where T : IComparable<T>
        {
            IEnumerator<T> includeEnumerator = include.GetEnumerator();
            IEnumerator<T> excludeEnumerator = exclude.GetEnumerator();

            if (!includeEnumerator.MoveNext()) yield break; // if nothing in the source, there is nothing

            if (excludeEnumerator.MoveNext())
            {
                while (true)
                {
                    int compare = includeEnumerator.Current.CompareTo(excludeEnumerator.Current);
                    if (compare < 0)
                    {
                        yield return includeEnumerator.Current;
                        if (!includeEnumerator.MoveNext()) yield break;
                    }
                    else if (compare > 0)
                    {
                        if (!excludeEnumerator.MoveNext()) break;
                    }
                    else
                    {
                        if (!includeEnumerator.MoveNext()) yield break;
                        if (!excludeEnumerator.MoveNext()) break;
                    }
                }
            }

            do
            {
                yield return includeEnumerator.Current;
            } while (includeEnumerator.MoveNext());
        }

        public static IEnumerable<DateTime> Until(this IEnumerable<DateTime> source, DateTime end)
        {
            return source.TakeWhile(d => d <= end);
        }
    }
}